package com.javaspeak.concurrency.nio.niolet;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.internal.TextListener;
import org.junit.runner.JUnitCore;

import com.javaspeak.concurrency.nio.niolet.client.ClientException;
import com.javaspeak.concurrency.nio.niolet.server.ServerException;
import com.javaspeak.concurrency.nio.niolet.server.ServerImpl;

/**
 * @author John Dickerson
 */
public class ServerTest {

    public Logger logger = Logger.getLogger( ServerTest.class );

    private Random random = new Random( ( new Date() ).getTime() );
    private String alphabet = "abcdefghijklmnopqrstuvwxyz";


    private String getRandomString( int length ){

        StringBuilder sb = new StringBuilder();

        for ( int i=0; i<length; i++ ){

            int index = random.nextInt( 25 );

            sb.append( alphabet.charAt( index ) );
        }

        return sb.toString();
    }


    @Test
    public void testServer() throws ClientException, ServerException,
        UnknownHostException, InterruptedException, ExecutionException {

        int port = 45;
        int defaultBufferSize = 750;
        int numberReadProcessingThreads = 2;

        int numberClients = 300;
        int numberMessagesPerClient = 300;
        int messageSize = 80;
        int numberSimultaneousClientsSendingMessages = 300;

        InetAddress localHostInetAddress = InetAddress.getLocalHost();
        String hostName = localHostInetAddress.getHostAddress();

        ServerImpl server = new ServerImpl(
                port, defaultBufferSize, numberReadProcessingThreads,
                    new ServerNioletMock() );

        server.start();

        ExecutorService executorService =
            Executors.newFixedThreadPool(
                    numberSimultaneousClientsSendingMessages );

        Collection<ClientRequest> clientRequests =
            new ArrayList<ClientRequest>();

        for ( int i=0; i<numberClients; i++ ){

            String[] messages = new String[ numberMessagesPerClient ];

            for ( int y=0; y<numberMessagesPerClient; y++ ){

                messages[ y ] = getRandomString( messageSize );
            }

            ClientRequest clientRequest =
                new ClientRequest( i, messages, hostName, port );

            clientRequests.add( clientRequest );
        }

        long startTime = new Date().getTime();

        List<Future<CallStatusEnum>> callStatusFutures =
            executorService.invokeAll( clientRequests );

        long endTime = new Date().getTime();

        CallStatusEnum callStatusEnum;

        for ( Future<CallStatusEnum> callStatusFuture : callStatusFutures ){

            callStatusEnum = callStatusFuture.get();

            assertEquals( false, callStatusFuture.isCancelled() );

            assertEquals( CallStatusEnum.PASSED, callStatusEnum );
        }

        double durationMilliseconds = endTime - startTime;
        double durationSecond = durationMilliseconds / 1000;
        double numberMessages = numberClients * numberMessagesPerClient;
        double numberMessagesPerSecond = numberMessages / durationSecond;

        double numberMessagesPerMinute = 60 * numberMessagesPerSecond;

        StringBuilder sb = new StringBuilder();
        sb.append( "\n" );
        sb.append( "======================================================\n" );
        sb.append( numberClients ).append( " clients each submitting " );
        sb.append( numberMessagesPerClient ).append( " messages.\n" );
        sb.append( "Each message was ").append( messageSize );
        sb.append( " characters long.\n" );
        sb.append( numberSimultaneousClientsSendingMessages );
        sb.append( " clients submitting asynchronously.\n" );
        sb.append( numberReadProcessingThreads );
        sb.append( " Read Processing threads used for processing messages.\n" );
        sb.append( "1 writing thread used for sending responses to " );
        sb.append( "clients\n\n" );
        sb.append( (long)numberMessages ).append( " messages processed in " );
        sb.append( (long)durationMilliseconds ).append( " milliseconds\n" );
        sb.append( "That is " ).append( (long)numberMessagesPerSecond );
        sb.append( " messages processed per second\n" );
        sb.append( "That is " ).append( (long)numberMessagesPerMinute );
        sb.append( " messages processed per minute\n" );
        sb.append( "======================================================\n" );

        logger.debug( sb.toString() );

        server.stopGracefully();

        Thread.sleep( 2000 );

        server.stopAggressively();

        executorService.shutdown();

        logger.debug( "executorService is terminated: " +
                executorService.isTerminated() );
    }


    public static void main( String args[] ){

        BasicConfigurator.resetConfiguration();
        BasicConfigurator.configure();

        JUnitCore junitCore = new JUnitCore();
        junitCore.addListener( new TextListener( System.out ) );
        junitCore.run( ServerTest.class );
    }
}
